```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>分布式Session共享解决方案 | 技术解析</title>
    <link href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        body {
            font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
            background-color: #f8fafc;
            color: #1e293b;
            line-height: 1.6;
        }
        .container {
            max-width: 1200px;
        }
        .hero-gradient {
            background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
        }
        .code-block {
            background-color: #1e293b;
            border-radius: 0.5rem;
            overflow: hidden;
        }
        .code-header {
            background-color: #334155;
            padding: 0.5rem 1rem;
            display: flex;
            align-items: center;
        }
        .code-dots {
            display: flex;
            gap: 0.5rem;
        }
        .code-dot {
            width: 12px;
            height: 12px;
            border-radius: 50%;
        }
        .code-dot-red {
            background-color: #ef4444;
        }
        .code-dot-yellow {
            background-color: #f59e0b;
        }
        .code-dot-green {
            background-color: #10b981;
        }
        .solution-card:hover {
            transform: translateY(-4px);
            box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
        }
        .mermaid {
            background-color: white;
            border-radius: 0.5rem;
            padding: 1rem;
            margin: 1rem 0;
        }
    </style>
</head>
<body class="min-h-screen">
    <!-- Hero Section -->
    <div class="hero-gradient text-white py-20">
        <div class="container mx-auto px-6">
            <div class="max-w-4xl mx-auto text-center">
                <h1 class="text-4xl md:text-5xl font-bold mb-6 leading-tight">分布式Session共享问题与解决方案</h1>
                <p class="text-xl opacity-90 mb-8">深入解析集群环境下的Session管理策略与Spring Session实现原理</p>
                <div class="flex justify-center space-x-4">
                    <span class="px-4 py-2 bg-white bg-opacity-20 rounded-full text-sm font-medium">分布式系统</span>
                    <span class="px-4 py-2 bg-white bg-opacity-20 rounded-full text-sm font-medium">Web开发</span>
                    <span class="px-4 py-2 bg-white bg-opacity-20 rounded-full text-sm font-medium">Spring生态</span>
                </div>
            </div>
        </div>
    </div>

    <!-- Main Content -->
    <div class="container mx-auto px-6 py-12">
        <!-- Problem Section -->
        <section class="mb-16">
            <div class="mb-8">
                <h2 class="text-3xl font-bold text-gray-800 mb-4">Session共享问题</h2>
                <div class="h-1 w-20 bg-indigo-500 rounded"></div>
            </div>
            <div class="bg-white rounded-xl shadow-md p-6">
                <p class="text-gray-700 mb-6 leading-relaxed">
                    HttpSession是通过Servlet容器创建和管理的，像Tomcat/Jetty都是保存在内存中的。而如果我们把web服务器搭建成分布式的集群，然后利用LVS或Nginx做负载均衡，那么来自同一用户的Http请求将有可能被分发到两个不同的web站点中去。那么问题就来了，如何保证不同的web站点能够共享同一份session数据呢？
                </p>
                <div class="bg-blue-50 border-l-4 border-blue-500 p-4 mb-6">
                    <div class="flex">
                        <div class="flex-shrink-0">
                            <i class="fas fa-info-circle text-blue-500 text-xl"></i>
                        </div>
                        <div class="ml-3">
                            <p class="text-sm text-blue-700">
                                关键问题：在分布式环境下，如何确保用户会话状态在集群中的各个节点间保持一致？
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </section>

        <!-- Solutions Section -->
        <section class="mb-16">
            <div class="mb-8">
                <h2 class="text-3xl font-bold text-gray-800 mb-4">Session共享解决方案</h2>
                <div class="h-1 w-20 bg-indigo-500 rounded"></div>
            </div>

            <!-- Solution Cards -->
            <div class="grid md:grid-cols-2 gap-8">
                <!-- Session Replication -->
                <div class="solution-card bg-white rounded-xl shadow-md p-6 transition-all duration-300">
                    <div class="flex items-center mb-4">
                        <div class="bg-indigo-100 p-2 rounded-lg mr-4">
                            <i class="fas fa-clone text-indigo-600 text-xl"></i>
                        </div>
                        <h3 class="text-xl font-bold text-gray-800">Session复制</h3>
                    </div>
                    <div class="mb-4">
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1651305084153-f18da983-48f2-42a3-8508-b107d128e958.png" alt="Session复制架构图" class="rounded-lg w-full">
                    </div>
                    <div class="pros-cons mb-4">
                        <div class="flex items-start mb-2">
                            <i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i>
                            <span class="text-gray-700">web-server (Tomcat) <span class="font-semibold">原生支持</span>，只需要修改配置文件</span>
                        </div>
                        <div class="flex items-start">
                            <i class="fas fa-times-circle text-red-500 mt-1 mr-2"></i>
                            <div>
                                <p class="text-gray-700">1. session同步需要数据传输，占用大量网络带宽，降低了服务器群的业务处理能力</p>
                                <p class="text-gray-700">2. 受到内存限制无法水平扩展更多的web-server</p>
                                <p class="text-gray-700">3. 大型分布式集群情况下不可取</p>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Client-side Storage -->
                <div class="solution-card bg-white rounded-xl shadow-md p-6 transition-all duration-300">
                    <div class="flex items-center mb-4">
                        <div class="bg-blue-100 p-2 rounded-lg mr-4">
                            <i class="fas fa-cookie text-blue-600 text-xl"></i>
                        </div>
                        <h3 class="text-xl font-bold text-gray-800">客户端存储</h3>
                    </div>
                    <div class="mb-4">
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1651305095875-f0c99271-a573-487e-8337-3b1075ac1801.png" alt="客户端存储架构图" class="rounded-lg w-full">
                    </div>
                    <div class="pros-cons mb-4">
                        <div class="flex items-start mb-2">
                            <i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i>
                            <span class="text-gray-700">服务器不需存储session,用户保存自己的session信息到cookie中，节省服务端资源</span>
                        </div>
                        <div class="flex items-start">
                            <i class="fas fa-times-circle text-red-500 mt-1 mr-2"></i>
                            <div>
                                <p class="text-gray-700">1. 每次http请求携带完整信息，浪费网络带宽</p>
                                <p class="text-gray-700">2. cookie有长度限制4K,不能保存大量信息</p>
                                <p class="text-gray-700">3. 存在泄漏、算改、窃取等安全隐患</p>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Hash Consistency -->
                <div class="solution-card bg-white rounded-xl shadow-md p-6 transition-all duration-300">
                    <div class="flex items-center mb-4">
                        <div class="bg-purple-100 p-2 rounded-lg mr-4">
                            <i class="fas fa-project-diagram text-purple-600 text-xl"></i>
                        </div>
                        <h3 class="text-xl font-bold text-gray-800">Hash一致性</h3>
                    </div>
                    <div class="mb-4">
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1651305103755-f25373a7-6286-4da4-b216-268d8b1c84e7.png" alt="Hash一致性架构图" class="rounded-lg w-full">
                    </div>
                    <div class="pros-cons mb-4">
                        <div class="flex items-start mb-2">
                            <i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i>
                            <div>
                                <p class="text-gray-700">1. 只需要改nginx配置，不需要修改应用代码</p>
                                <p class="text-gray-700">2. 负载均衡，只要hash属性的值分布是均匀的</p>
                                <p class="text-gray-700">3. 可以支持web-server水平扩展</p>
                            </div>
                        </div>
                        <div class="flex items-start">
                            <i class="fas fa-times-circle text-red-500 mt-1 mr-2"></i>
                            <div>
                                <p class="text-gray-700">1. web-server重启可能导致部分session丢失</p>
                                <p class="text-gray-700">2. 水平扩展后rehash会路由不到正确的session</p>
                                <p class="text-gray-700">但问题不大，因为session本身都有有效期</p>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Unified Storage -->
                <div class="solution-card bg-white rounded-xl shadow-md p-6 transition-all duration-300">
                    <div class="flex items-center mb-4">
                        <div class="bg-red-100 p-2 rounded-lg mr-4">
                            <i class="fas fa-database text-red-600 text-xl"></i>
                        </div>
                        <h3 class="text-xl font-bold text-gray-800">统一存储</h3>
                    </div>
                    <div class="mb-4">
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1651305118070-d914713b-323a-4a76-9b39-22c3850cb6cd.png" alt="统一存储架构图" class="rounded-lg w-full">
                    </div>
                    <div class="pros-cons mb-4">
                        <div class="flex items-start mb-2">
                            <i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i>
                            <div>
                                <p class="text-gray-700">1. 没有安全隐患</p>
                                <p class="text-gray-700">2. 可以水平扩展，数据库/缓存水平切分即可</p>
                                <p class="text-gray-700">3. web-server重启或者扩容都不会有session丢失</p>
                            </div>
                        </div>
                        <div class="flex items-start">
                            <i class="fas fa-times-circle text-red-500 mt-1 mr-2"></i>
                            <div>
                                <p class="text-gray-700">增加了一次网络调用，并且需要修改应用代码</p>
                                <p class="text-gray-700">redis获取数据比内存慢很多</p>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>

        <!-- Distributed Session Section -->
        <section class="mb-16">
            <div class="mb-8">
                <h2 class="text-3xl font-bold text-gray-800 mb-4">分布式Session</h2>
                <div class="h-1 w-20 bg-indigo-500 rounded"></div>
            </div>
            <div class="bg-white rounded-xl shadow-md p-6">
                <p class="text-gray-700 mb-6 leading-relaxed">
                    <strong>分布式Session</strong> 就是我们的 Session 会话对于集群中的每个服务共享。我这里举一个例子：
                </p>
                <div class="bg-indigo-50 rounded-lg p-4 mb-6">
                    <p class="text-gray-800">
                        假如一个商品服务通过2台机器 A和B 进行集群部署，用户访问 A服务器登录成功后，将此用户Session信息保存在 A 服务器上。突然A服务器突然宕机，用户访问跳转B服务器上。此时B服务器上并没有A服务的 Session。用户需要再次执行登录操作。而Spring Session 可以将用户登录后的Session 保存在Redis或者数据库中，当 A 服务器宕机切换到 B 服务器时，此时B服务器从Redis中获取Session,无需在进行登录操作。
                    </p>
                </div>
            </div>
        </section>

        <!-- SpringSession Section -->
        <section class="mb-16">
            <div class="mb-8">
                <h2 class="text-3xl font-bold text-gray-800 mb-4">SpringSession</h2>
                <div class="h-1 w-20 bg-indigo-500 rounded"></div>
            </div>
            <div class="bg-white rounded-xl shadow-md p-6">
                <div class="flex items-center mb-4">
                    <i class="fas fa-book text-indigo-500 mr-2"></i>
                    <a href="https://docs.spring.io/spring-session/docs/2.4.4/reference/html5/guides/boot-redis.html" class="text-indigo-600 hover:text-indigo-800" target="_blank">官方文档地址</a>
                </div>
                <div class="prose max-w-none">
                    <p class="text-gray-700 mb-4">
                        Spring Session提供了用于管理用户会话信息的API和实现。Spring Session使得支持集群会话变得微不足道，而不依赖于特定于应用程序容器的解决方案。它还提供透明集成：
                    </p>
                    <ul class="list-disc pl-6 mb-4 text-gray-700">
                        <li>HttpSession - 允许以应用程序容器（即Tomcat）中立的方式替换HttpSession，支持在头文件中提供会话ID以使用RESTful API</li>
                        <li>WebSocket - 提供在接收WebSocket消息时保持HttpSession活动的能力</li>
                        <li>WebSession - 允许以应用程序容器中立方式替换Spring WebFlux的WebSession</li>
                    </ul>
                    <p class="text-gray-700">
                        从官方介绍中可以了解到 Spring Session 是用来解决，集群下实现全局 Session的一个框架实现。全局 Session 也可以理解成<strong>分布式Session</strong>
                    </p>
                </div>
            </div>
        </section>

        <!-- SpringBoot Integration -->
        <section class="mb-16">
            <div class="mb-8">
                <h2 class="text-3xl font-bold text-gray-800 mb-4">SpringBoot整合SpringSession</h2>
                <div class="h-1 w-20 bg-indigo-500 rounded"></div>
            </div>

            <!-- Dependencies -->
            <div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
                <div class="code-header">
                    <div class="code-dots mr-4">
                        <div class="code-dot code-dot-red"></div>
                        <div class="code-dot code-dot-yellow"></div>
                        <div class="code-dot code-dot-green"></div>
                    </div>
                    <span class="text-gray-300 text-sm">pom.xml</span>
                </div>
                <div class="p-4">
                    <pre class="text-gray-200 overflow-x-auto"><code>&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.session&lt;/groupId&gt;
    &lt;artifactId&gt;spring-session-data-redis&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
                </div>
            </div>

            <!-- YAML Config -->
            <div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
                <div class="code-header">
                    <div class="code-dots mr-4">
                        <div class="code-dot code-dot-red"></div>
                        <div class="code-dot code-dot-yellow"></div>
                        <div class="code-dot code-dot-green"></div>
                    </div>
                    <span class="text-gray-300 text-sm">application.yml</span>
                </div>
                <div class="p-4">
                    <pre class="text-gray-200 overflow-x-auto"><code>server:
  port: 8001
  servlet:
    session:
      timeout: 30m # session超时时间，默认也是30m
spring:
  session:
    store-type: redis # session储存的位置
    redis:
      namespace: spring:session # session储存到redis的前缀
  redis:
    password: root
    host: 192.168.147.102</code></pre>
                </div>
            </div>

            <!-- Main Class -->
            <div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
                <div class="code-header">
                    <div class="code-dots mr-4">
                        <div class="code-dot code-dot-red"></div>
                        <div class="code-dot code-dot-yellow"></div>
                        <div class="code-dot code-dot-green"></div>
                    </div>
                    <span class="text-gray-300 text-sm">SpringSession01Application.java</span>
                </div>
                <div class="p-4">
                    <pre class="text-gray-200 overflow-x-auto"><code>@EnableRedisHttpSession
@SpringBootApplication(scanBasePackages = "com.ts")
public class SpringSession01Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringSession01Application.class, args);
    }
}</code></pre>
                </div>
            </div>

            <!-- Model -->
            <div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
                <div class="code-header">
                    <div class="code-dots mr-4">
                        <div class="code-dot code-dot-red"></div>
                        <div class="code-dot code-dot-yellow"></div>
                        <div class="code-dot code-dot-green"></div>
                    </div>
                    <span class="text-gray-300 text-sm">User.java</span>
                </div>
                <div class="p-4">
                    <pre class="text-gray-200 overflow-x-auto"><code>@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String username;
}</code></pre>
                </div>
            </div>

            <!-- Controller -->
            <div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
                <div class="code-header">
                    <div class="code-dots mr-4">
                        <div class="code-dot code-dot-red"></div>
                        <div class="code-dot code-dot-yellow"></div>
                        <div class="code-dot code-dot-green"></div>
                    </div>
                    <span class="text-gray-300 text-sm">UserController.java</span>
                </div>
                <div class="p-4">
                    <pre class="text-gray-200 overflow-x-auto"><code>@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/login")
    public String login(String username, String password, HttpServletRequest request) {
        if ("admin".equals(username) && "admin".equals(password)) {
            request.getSession().setAttribute("user", new User(username));
            return "success --> "+System.currentTimeMillis();
        } else {
            return "error --> "+System.currentTimeMillis();
        }
    }

    @GetMapping("/getUser")
    public String getUser(HttpServletRequest request) {
        Object user = request.getSession().getAttribute("user");
        if(user == null){
            return "你还没有登录";
        }
        return user.toString() +" --> "+System.currentTimeMillis();
    }
}</code></pre>
                </div>
            </div>

            <!-- Cluster Demo -->
            <div class="mb-8">
                <h3 class="text-xl font-bold text-gray-800 mb-4">在启动一个服务模拟集群</h3>
                <div class="bg-white rounded-xl shadow-md p-4">
                    <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1651305150386-b2c077af-f409-433a-a6e5-adb239b4e1f6.png" alt="集群模拟" class="rounded-lg w-full">
                </div>
            </div>

            <!-- Test -->
            <div class="mb-8">
                <h3 class="text-xl font-bold text-gray-800 mb-4">测试</h3>
                <div class="bg-white rounded-xl shadow-md p-4">
                    <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1651305160637-1bca6d40-decf-449c-ac66-09b693d8e48d.png" alt="测试结果" class="rounded-lg w-full">
                </div>
            </div>

            <!-- JSON Serialization -->
            <div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
                <div class="code-header">
                    <div class="code-dots mr-4">
                        <div class="code-dot code-dot-red"></div>
                        <div class="code-dot code-dot-yellow"></div>
                        <div class="code-dot code-dot-green"></div>
                    </div>
                    <span class="text-gray-300 text-sm">SpringSessionConfig.java</span>
                </div>
                <div class="p-4">
                    <pre class="text-gray-200 overflow-x-auto"><code>@Configuration
public class SpringSessionConfig {
    /**
     * JSON序列化 - 使用FastJson，需要引入Fastjson的依赖
     */
    @Bean
    public RedisSerializer&lt;Object&gt; springSessionDefaultRedisSerializer(){
        return new GenericFastJsonRedisSerializer();
    }
    
     /**
     * 解决Cookie跨域问题
     */
    @Bean
    public CookieSerializer cookieSerializer(){
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        cookieSerializer.setDomainName("ts.com");
        cookieSerializer.setCookieName("sessionId");  // 自定义SessionId
        return cookieSerializer;
    }
}</code></pre>
                </div>
            </div>

            <!-- Fastjson Dependency -->
            <div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
                <div class="code-header">
                    <div class="code-dots mr-4">
                        <div class="code-dot code-dot-red"></div>
                        <div class="code-dot code-dot-yellow"></div>
                        <div class="code-dot code-dot-green"></div>
                    </div>
                    <span class="text-gray-300 text-sm">pom.xml</span>
                </div>
                <div class="p-4">
                    <pre class="text-gray-200 overflow-x-auto"><code>&lt;dependency&gt;
    &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
    &lt;artifactId&gt;fastjson&lt;/artifactId&gt;
    &lt;version&gt;1.2.76&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
                </div>
            </div>
        </section>

        <!-- SpringSession Principle -->
        <section class="mb-16">
            <div class="mb-8">
                <h2 class="text-3xl font-bold text-gray-800 mb-4">SpringSession原理</h2>
                <div class="h-1 w-20 bg-indigo-500 rounded"></div>
            </div>
            <div class="bg-white rounded-xl shadow-md p-6">
                <p class="text-gray-700 mb-6">
                    SpringSession核心对象有两个
                </p>
                <ol class="list-decimal pl-6 mb-6 space-y-4">
                    <li class="text-gray-700"><strong>SessionRepository</strong>：主要完成Session的CRUD操作</li>
                    <li class="text-gray-700"><strong>SessionRepositoryFilter</strong>：实现了Filter接口，主要完成拦截</li>
                    <li class="text-gray-700"><strong>SessionRepositoryRequestWrapper</strong>：实现了HttpServletRequest接口，是SpringSession中调用的session，而并不是原生的session。</li>
                </ol>

                <div class="grid md:grid-cols-2 gap-8">
                    <div class="mb-6">
                        <h3 class="text-xl font-bold text-gray-800 mb-4">SessionRepository</h3>
                        <div class="bg-white rounded-lg shadow-md p-4">
                            <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1651305180388-d2b74daf-40ff-4e0b-850e-e50911437451.png" alt="SessionRepository" class="rounded-lg w-full">
                        </div>
                    </div>
                    <div class="mb-6">
                        <h3 class="text-xl font-bold text-gray-800 mb-4">SessionRepositoryFilter</h3>
                        <div class="bg-white rounded-lg shadow-md p-4">
                            <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1651305191653-d02fb23f-c6a3-4b2c-8213-72ff7975ef78.png" alt="SessionRepositoryFilter" class="rounded-lg w-full">
                            <p class="text-gray-700 mt-4">拦截所有的请求</p>
                        </div>
                    </div>
                </div>

                <div class="mb-6">
                    <div class="bg-white rounded-lg shadow-md p-4">
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1651305202455-4182e6a1-fc76-46cc-ad41-fe367b735978.png" alt="Filter Process" class="rounded-lg w-full">
                        <p class="text-gray-700 mt-4">复写了getSession方法，在这个中从redis中读取session。</p>
                    </div>
                </div>

                <div class="grid md:grid-cols-2 gap-8">
                    <div class="mb-6">
                        <div class="bg-white rounded-lg shadow-md p-4">
                            <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1651305220600-83aff8c0-e677-49cd-8497-9efe36ebee18.png" alt="Code Example 1" class="rounded-lg w-full">
                        </div>
                    </div>
                    <div class="mb-6">
                        <div class="bg-white rounded-lg shadow-md p-4">
                            <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1651305227968-59d3d8b3-aa9a-4580-9133-52232527c793.png" alt="Code Example 2" class="rounded-lg w-full">
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </div>

    <script>
        mermaid.initialize({
            startOnLoad: true,
            theme: 'default',
            flowchart: {
                useMaxWidth: true,
                htmlLabels: true,
                curve: 'basis'
            }
        });
    </script>
</body>
</html>
```